package org.lestr.secuel.m3ds.importation.univers;

import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Collection;

import org.lestr.secuel.m3ds.Camera;
import org.lestr.secuel.m3ds.Univers;
import org.lestr.secuel.m3ds.IUsine;
import org.lestr.secuel.m3ds.lumieres.Lumiere;
import org.lestr.secuel.m3ds.maths.ICoordonnees3d;
import org.lestr.secuel.m3ds.objets3D.Materiau;
import org.lestr.secuel.m3ds.objets3D.IObjet3d;
import org.lestr.secuel.m3ds.objets3D.ITriangle;

public class Scene3DSImportateur implements UniversImportateur {

    // >------ Primary chunk

    public static final int MAIN3DS = 0x4D4D;

    // >------ Main Chunks

    public static final int EDIT3DS = 0x3D3D; // this is the start of the
    // editor config


    public static final int KEYF3DS = 0xB000; // this is the start of the
    // keyframer config

    // >------ sub defines of EDIT3DS

    public static final int EDIT_CONFIG1 = 0x0100;


    public static final int EDIT_CONFIG2 = 0x3E3D;


    public static final int EDIT_VIEW_P1 = 0x7012;


    public static final int EDIT_VIEW_P2 = 0x7011;


    public static final int EDIT_VIEW_P3 = 0x7020;


    public static final int EDIT_VIEW1 = 0x7001;


    public static final int EDIT_BACKGR = 0x1200;


    public static final int EDIT_AMBIENT = 0x2100;


    public static final int EDIT_OBJECT = 0x4000;


    public static final int EDIT_UNKNW01 = 0x1100;


    public static final int EDIT_UNKNW02 = 0x1201;


    public static final int EDIT_UNKNW03 = 0x1300;


    public static final int EDIT_UNKNW04 = 0x1400;


    public static final int EDIT_UNKNW05 = 0x1420;


    public static final int EDIT_UNKNW06 = 0x1450;


    public static final int EDIT_UNKNW07 = 0x1500;


    public static final int EDIT_UNKNW08 = 0x2200;


    public static final int EDIT_UNKNW09 = 0x2201;


    public static final int EDIT_UNKNW10 = 0x2210;


    public static final int EDIT_UNKNW11 = 0x2300;


    public static final int EDIT_UNKNW12 = 0x2302;


    public static final int EDIT_UNKNW13 = 0x3000;

    // Materiaux

    public static final int MAT_MAIN = 0xAFFF;


    public static final int MAT_NAME = 0xA000;


    public static final int MAT_AMBIENT_COL = 0xA010;


    public static final int MAT_DIFFUSE_COL = 0xA020;


    public static final int MAT_SPECULAR_COL = 0xA030;


    public static final int MAT_SHININESS = 0xA040;


    public static final int MAT_SHININESS_STRENGTH = 0xA041;


    public static final int MAT_TRANSPARENCY = 0xA050;


    public static final int MAT_TRANSPARENCY_FALLOFF = 0xA052;


    public static final int MAT_MATERIAL_TYPE = 0xA100;


    public static final int MAT_SELF_ILLUM = 0xA084;


    public static final int MAT_UNKNW01 = 0xA087;


    public static final int MAT_TRANSPARENCY_FALLOFF_AMOUNT = 0xA240;


    public static final int MAT_REFLECT_BLUR = 0xA250;


    public static final int MAT_TWO_SIDED = 0xA081;


    public static final int MAT_TRANSPARENCY_ADD = 0xA083;


    public static final int MAT_WIRE_ON = 0xA085;


    public static final int MAT_FACE_MAP = 0xA088;


    public static final int MAT_TRANSPARENCY_IN = 0xA08A;


    public static final int MAT_SOFTEN = 0xA08C;


    public static final int MAT_WIRE_THINCKNESS_IN_UNIT = 0xA08E;


    public static final int MAT_WIRE_THINCKNESS = 0xA087;
    //public static final int MAT_ = 0xA;


    // >------ sub defines of EDIT_OBJECT
    public static final int OBJ_TRIMESH = 0x4100;


    public static final int OBJ_LIGHT = 0x4600;


    public static final int OBJ_CAMERA = 0x4700;


    public static final int OBJ_UNKNWN01 = 0x4010;


    public static final int OBJ_UNKNWN02 = 0x4012; // >>---- Could be shadow

    // >------ sub defines of OBJ_CAMERA

    public static final int CAM_UNKNWN01 = 0x4710;


    public static final int CAM_UNKNWN02 = 0x4720;

    // >------ sub defines of OBJ_LIGHT

    public static final int LIT_OFF = 0x4620;


    public static final int LIT_SPOT = 0x4610;


    public static final int LIT_UNKNWN01 = 0x465A;

    // >------ sub defines of OBJ_TRIMESH

    public static final int TRI_VERTEXL = 0x4110;


    public static final int TRI_FACEL1 = 0x4120;


    public static final int TRI_SMOOTH = 0x4150;


    public static final int TRI_LOCAL = 0x4160;


    public static final int TRI_VISIBLE = 0x4165;


    public static final int TRI_VERTEXOPTIONS = 0x4111;


    public static final int TRI_MAPPINGCOORS = 0x4140;


    public static final int TRI_MAPPINGSTANDARD = 0x4170;


    public static final int TRI_MATERIAL = 0x4130;

    // >>------ sub defs of KEYF3DS

    public static final int KEYF_UNKNWN01 = 0xB009;


    public static final int KEYF_UNKNWN02 = 0xB00A;


    public static final int KEYF_FRAMES = 0xB008;


    public static final int KEYF_OBJDES = 0xB002;

    // >>------ these define the different color chunk types

    public static final int COL_RGB = 0x0010;


    public static final int COL_TRU = 0x0011;


    public static final int COL_UNK = 0x0013;

    // >>------ defines for viewport chunks

    public static final int TOP = 0x0001;


    public static final int BOTTOM = 0x0002;


    public static final int LEFT = 0x0003;


    public static final int RIGHT = 0x0004;


    public static final int FRONT = 0x0005;


    public static final int BACK = 0x0006;


    public static final int USER = 0x0007;


    public static final int CAMERA = 0x0008; // 0xFFFF is the actual code
    // read from file


    public static final int LIGHT = 0x0009;


    public static final int DISABLED = 0x0010;


    public static final int BOGUS = 0x0011;


    public static final int TAILLE_TAMPON = 1024 * 1024 * 2;


    // Déclaration des attributs
    protected IUsine usine;


    private RandomAccessFile lecteur;


    private IObjet3d objetCourant;


    private Univers univers;


    private ArrayList<ICoordonnees3d> points;


    private String nomCourant;


    private ArrayList<ITriangle> triangles;


    private ArrayList<Materiau> materiaux;


    private Materiau matériauCourant;


    /** Constructeur */
    public Scene3DSImportateur() {

        // Instanciation des attributs
        usine = null;
        lecteur = null;
        matériauCourant = null;
        materiaux = null;

    }// FIN Scene3DSImportateur


    @Override
    public Iterable<Univers> importer(File fichier,
                                      IUsine usine) throws UniversImportException {

        try {

            // Mémorise l'usine
            this.usine = usine;

            // Ouvre un lecteur sur le fichier
            lecteur = new RandomAccessFile(fichier, "r");

            // Compile
            try {
                traiterChunk();
            } catch (EOFException e) {
            }

            // Ferme le lecteur
            lecteur.close();

        } catch (Exception e) {

            // La propage
            throw (new UniversImportException());

        }

        // Construit le résultat
        Collection<Univers> rslt = new ArrayList<Univers>();
        rslt.add(univers);

        // Le retourne
        return rslt;

    }// FIN importer


    private void traiterChunk() throws IOException, EOFException {

        // Variables locales
        float x = 0, y = 0, z = 0;
        float cibleX = 0, cibleY = 0, cibleZ = 0;
        float rouge = 1, vert = 1, bleu = 1;
        boolean couleur255 = false;
        boolean spot = false;
        boolean lumièreEnCréation = false;

        // Initialisation
        materiaux = new ArrayList<Materiau>();

        // Récupère des informations sur le chunk
        int chunkId = lireChunkID();
        int chunkTaille = lireChunkTaille();

        // Selon le type de chunk
        switch (chunkId) {

            case MAIN3DS:

                univers = usine.univers();

                System.out.println("MAIN3DS");
                break;

            case EDIT3DS:
                System.out.println("EDIT3DS");
                break;

            case MAT_MAIN:
                System.out.println("MAT_MAIN");
                break;

            case MAT_NAME:

                // S'il existe, ajoute le matériau courant à la liste des matériaux
                if (matériauCourant != null)
                    materiaux.add(matériauCourant);

                // Initialise le matériau courant
                matériauCourant = usine.matériau();
                matériauCourant.définirNom(lireChaine());

                System.out.println("MAT_NAME");
                break;

            case EDIT_CONFIG1:
            case EDIT_CONFIG2:
            case EDIT_VIEW_P1:
            case EDIT_VIEW_P2:
            case EDIT_VIEW_P3:
            case EDIT_VIEW1:
            case EDIT_BACKGR:
            case EDIT_AMBIENT:
            case EDIT_UNKNW01:
            case EDIT_UNKNW02:
            case EDIT_UNKNW03:
            case EDIT_UNKNW04:
            case EDIT_UNKNW05:
            case EDIT_UNKNW06:
            case EDIT_UNKNW07:
            case EDIT_UNKNW08:
            case EDIT_UNKNW09:
            case EDIT_UNKNW10:
            case EDIT_UNKNW11:
            case EDIT_UNKNW12:
            case EDIT_UNKNW13:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                lecteur.skipBytes(chunkTaille - 6);
                System.out.println("EDIT_CONFIG2");
                break;

            case EDIT_OBJECT:

                // Récupère le nom de l'objet
                nomCourant = lireChaine();

                System.out.println("EDIT_OBJECT");
                break;

            case OBJ_TRIMESH:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                // Début de construction d'un nouvel objet
                objetCourant = usine.objet3D();
                objetCourant.définirNom(nomCourant);

                // A FAIRE

                // Initialise la liste des points
                points = new ArrayList<ICoordonnees3d>();
                triangles = new ArrayList<ITriangle>();

                System.out.println("OBJ_TRIMESH");
                break;

            case TRI_VERTEXOPTIONS:
            case TRI_MAPPINGCOORS:
            case TRI_MAPPINGSTANDARD:
            case TRI_SMOOTH:
            case TRI_MATERIAL:
            case TRI_LOCAL:
            case TRI_VISIBLE:
                lecteur.skipBytes(chunkTaille - 6);
                System.out.println("TRI_VERTEXOPTIONS");
                break;

            case OBJ_LIGHT:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                // Une lumière est un cours de création
                lumièreEnCréation = true;

                // Récupère les coordonnées de la lumière
                x = lireFloat() / 100;
                y = lireFloat() / 100;
                z = lireFloat() / 100;
                spot = false;

                System.out.println("OBJ_LIGHT");
                break;

            case LIT_SPOT:

                // La lumière courante est un spot
                spot = true;

                // Récupère les coordonnées de la cible du spot
                cibleX = lireFloat();
                cibleY = lireFloat();
                cibleZ = lireFloat();

                // Saute "Hotspot" et "Falloff"
                lireFloat();
                lireFloat();

                System.out.println("LIT_SPOT");
                break;

            case COL_RGB:

                // Récupère les coordonnées de la lumière
                rouge = lireFloat();
                vert = lireFloat();
                bleu = lireFloat();
                couleur255 = false;

                System.out.println("COL_RGB");
                break;

            case COL_TRU:

                // Récupère les coordonnées de la lumière
                rouge = lireOctet();
                vert = lireOctet();
                bleu = lireOctet();
                couleur255 = true;

                System.out.println("COL_TRU");
                break;

            case OBJ_CAMERA:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                // Récupère les coordonnées de la caméra
                x = lireFloat() / 100;
                y = lireFloat() / 100;
                z = lireFloat() / 100;

                // Récupère les coordonnées du point vers lequel s'oriente la caméra
                cibleX = lireFloat() / 100;
                cibleY = lireFloat() / 100;
                cibleZ = lireFloat() / 100;

                // Saute "camera lens" et "camera angle"
                lireFloat();
                lireFloat();

                // Construit la caméra
                Camera caméra = usine.caméra();
                caméra.setX(x);
                caméra.setY(y);
                caméra.setZ(z);
                caméra.pointer(cibleX, cibleY, cibleZ);

                // L'ajoute à l'univers
                univers.ajoutCaméra(caméra);

                System.out.println("OBJ_CAMERA");
                break;

            case OBJ_UNKNWN01:
            case OBJ_UNKNWN02:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                lecteur.skipBytes(chunkTaille - 6);
                System.out.println("OBJ_UNKNWN");
                break;

            case TRI_VERTEXL:

                // Lit le nombre de points à lire
                int nbPoints = lireUnsignedShort();

                // Parcours les points à lire
                for (int i = 0; i <= nbPoints - 1; i = i + 1) {

                    // Récupère les coordonnées du point courant
                    x = lireFloat() / 100;
                    y = lireFloat() / 100;
                    z = lireFloat() / 100;

                    // Construit le point
                    ICoordonnees3d pointCourant = usine.nouvellesCoordonnées(x, y, z);

                    // L'ajoute à la liste des points
                    points.add(pointCourant);

                }

                System.out.println("TRI_VERTEXL");
                break;

            case TRI_FACEL1:

                // Lit le nombre de triangles à lire
                int nbTriangles = lireUnsignedShort();

                // Parcours les triangles à lire
                for (int i = 0; i <= nbTriangles - 1; i = i + 1) {

                    // Récupère les id des points du triangle
                    int p1 = lireUnsignedShort();
                    int p2 = lireUnsignedShort();
                    int p3 = lireUnsignedShort();
                    lireUnsignedShort();

                    // Construit le triangle
                    ITriangle triangleCourant = usine.triangle(points.get(p1),
                                                              points.get(p2),
                                                              points.get(p3));

                    // L'ajoute à la liste des points
                    triangles.add(triangleCourant);

                }

                // Ajoute les triangles à l'objet
                objetCourant.ajouterTriangles(triangles);

                System.out.println("TRI_FACEL1");
                break;

            case KEYF3DS:

                // Ajoute, s'il existe, l'objet 3D précédement construit à l'éditeur
                if (objetCourant != null) {
                    univers.ajoutObjet3D(objetCourant);
                    objetCourant = null;
                }

                // Construit et ajoute, si elle existe, la lumière en cours de création
                if (lumièreEnCréation) {
                    univers.ajoutLumière(créerLumière(x, y, z, spot, cibleX, cibleY, cibleZ, couleur255, rouge, vert, bleu));
                    lumièreEnCréation = false;
                }

                lecteur.skipBytes(chunkTaille - 6);
                System.out.println("KEYF3DS");
                break;

            default:

                // Saute le chunk
                lecteur.skipBytes(chunkTaille - 6);

                System.out.println("Chunk Inconnu");
                break;

        }

        // Lit le suivant
        traiterChunk();

    }// FIN traiterChunk


    private Lumiere créerLumière(float x,
                                 float y,
                                 float z,
                                 boolean spot,
                                 float cibleX,
                                 float cibleY,
                                 float cibleZ,
                                 boolean couleur255,
                                 float rouge,
                                 float vert,
                                 float bleu) {

        return null; // TODO

    }// FIN créerLumière


    protected int lireChunkID() throws IOException {

        return lireUnsignedShort();

    }// FIN lireChunkID


    protected int lireChunkTaille() throws IOException {

        return lireUnsignedInt();

    }// FIN lireChunkID


    protected int lireUnsignedShort() throws IOException {

        int num = lecteur.readUnsignedShort();

        return ((num << 8) & 0xFF00) | ((num >> 8) & 0x00FF);

    }// FIN readUnsignedShort


    protected byte lireOctet() throws IOException {

        return lecteur.readByte();

    }// FIN lireOctet


    protected int lireUnsignedInt() throws IOException {

        int num = lecteur.readInt();

        return ((num << 24) & 0xFF000000)
               | ((num << 8) & 0x00FF0000)
               | ((num >> 8) & 0x0000FF00)
               | ((num >> 24) & 0x000000FF);

    }// FIN readUnsignedInt


    protected float lireFloat() throws IOException {

        return Float.intBitsToFloat(lireUnsignedInt());

    }// FIN readFloat


    protected String lireChaine() throws IOException {

        StringBuffer tampon = new StringBuffer();
        char c;

        while ((c = (char) lecteur.readUnsignedByte()) != '\0')
            tampon.append(c);

        return tampon.toString();

    }// FIN lireChaine


    @Override
    public String getFormatExtension() {

        return "3ds";

    }// FIN getFormatExtension


    @Override
    public String getFormatNom() {

        return "3D-Studio";

    }// FIN 


}// FIN Classe
